#ifndef __SUBSYSTEM_TERRAIN_H__
#define __SUBSYSTEM_TERRAIN_H__

#include <mutex>
#include <vector>
#include <map>
#include <set>
#include <memory>
#include <functional>

#include "Game/TerrainRaycastResult.hpp"
#include "Game/BlockDropValue.hpp"
#include "Subsystem.hpp"
#include "Utils/Utils.h"
#include "Terrain/TerrainChunk.h"
#include "Terrain/ITerrainSerializer.hpp"
#include "Terrain/ITerrainContentsGenerator.h"
#include "concurrentqueue.h"
#include "blockingconcurrentqueue.h"
#include "GameLock.hpp"

namespace PocketSurvival
{
    struct ComponentMiner;

    struct CellChange
    {
        int32_t x;
        int32_t y;
        int32_t z;
        int32_t value;
    };

    class TerrainUpdater;
    class SubsystemBlockBehaviors;
    class SubsystemPickables;

    class SubsystemTerrain : public Subsystem
    {
    public:
        /**
         * @brief 获取对应位置的 区块，若返回nullptr的话，记载区块表中没找到，可以调用 getTerrainChunk 加载
         * @param coordX 区块位置
         * @param coordY 区块位置
        */
        virtual TerrainChunk *getTerrainChunk(int32_t coordX, int32_t coordY);
        inline  TerrainChunk *getChunkAtCell(int32_t x, int32_t y);

        /**
         * @brief 加载对应位置的 区块， 当从 getTerrainChunk 读取的 区块 为空时可以调用该函数，让后面的 地图 处理时生成对应的区块出来
         * 注： 调用该函数后不会立即加载生成对应的 区块，而是要等到下次的 Tick 到来时才会生成
         * @param coordX 区块位置
         * @param coordY 区块位置
        */
        virtual void    loadTerrrainChunk(int32_t coordX, int32_t coordY);

        virtual int32_t getCellValue(int32_t x, int32_t y, int32_t z);
        virtual void    setCellValue(int32_t x, int32_t y, int32_t z, int32_t value);

        /**
         * @brief 获取对应位置的最高点
         * @param x 方块坐标x
         * @param z 方块坐标z
         * @return 最高点y的值
        */
        virtual int32_t calculateTopmostCellHeight(int32_t x, int32_t z);

        virtual int32_t getShaftValue(int32_t x, int32_t z);
        virtual void    setShaftValue(int32_t x, int32_t z, int32_t value);

        inline int32_t getTemperature(int32_t x, int32_t z);
        inline void    setTemperature(int32_t x, int32_t z, int32_t temperature);
        inline int32_t getHumidity(int32_t x, int32_t z);
        inline void    setHumidity(int32_t x, int32_t z, int32_t humidity);
        inline int32_t getTopHeight(int32_t x, int32_t z);
        inline void    setTopHeight(int32_t x, int32_t z, int32_t topHeight);
        inline int32_t getBottomHeight(int32_t x, int32_t z);
        inline void    setBottomHeight(int32_t x, int32_t z, int32_t bottomHeight);

        inline int32_t getSeasonalTemperature(int32_t x, int32_t y);
        inline int32_t getSeasonalTemperature(int32_t shaftValue);
        inline void    setSeasonalTemperature(int32_t x, int32_t y, int32_t seasonTemperature);
        inline int32_t getSeasonalHumidity(int32_t x, int32_t y);
        inline int32_t getSeasonalHumidity(int32_t shaftValue);
        inline void    setSeasonalHumidity(int32_t x, int32_t y, int32_t seasonHumidity);

        inline int32_t getSeasonTemperatureValue();
        inline void    setSeasonTemperatureValue(int32_t value);
        inline int32_t getSeasonHumidityValue();
        inline void    setSeasonHumidityValue(int32_t value);


        inline int32_t getCellContents(int32_t x, int32_t y, int32_t z);
        inline int32_t getCellContents(const Point3 &point);
        inline int32_t getCellValue(const Point3 &point);

        inline int32_t getCellLight(int32_t x, int32_t y, int32_t z);
        inline int32_t getCellLight(const Point3 &point);

        std::shared_ptr<TerrainRaycastResult> raycast(const Vector3 &start, const Vector3 &end, bool useInteractionBoxes, bool skipAirBlocks, std::function<bool(int,float)> func);

        /**
         * @brief 改变方块
         * @param x 方块坐标x
         * @param y 方块坐标y
         * @param z 方块坐标z
         * @param value 改变的方块id
         * @param miner 是谁改变的
        */
        virtual void changeCell(int32_t x, int32_t y, int32_t z, int32_t value, ComponentMiner *miner = nullptr);
        inline void changeCell(const Point3& point, int32_t value, ComponentMiner *miner = nullptr);

        /**
         * @brief 破坏并改变方块
         * @param toolLevel 使用的工具等级
         * @param x 方块坐标x
         * @param y 方块坐标y
         * @param z 方块坐标z
         * @param newValue 改变的方块id
         * @param noDrop 是否不掉落物品
         * @param miner 是谁改变的
        */
        virtual void destroyCell(int32_t toolLevel, int32_t x, int32_t y, int32_t z, int32_t newValue, bool noDrop, ComponentMiner *miner = nullptr);

        /**
         * @brief 处理单元格变化
         * 当方块被破坏或者是放置时，其方块及周围的方块会被触发
        */
        virtual void processModifiedCells();

        /**
         * @brief 改变旧方块，
         * 
         * 已内部加锁的方式，保证相应位置的方块必须是旧方块值才会进行改变，若内部的方块值已经被其它线程修改，则不改变对应的方块值
         * 
         * @param x 方块坐标x
         * @param y 方块坐标y
         * @param z 方块坐标z
         * @param oldValue 旧方块值
         * @param newValue 改变的方块id
         * @param miner 是谁改变的
        */
        virtual bool changeOldCell(int32_t x, int32_t y, int32_t z, int32_t oldValue, int32_t newValue, ComponentMiner *miner = nullptr);
        inline bool changeOldCell(const Point3 &point, int32_t oldValue, int32_t newValue, ComponentMiner *miner = nullptr); 

        /**
         * @brief 获取所有区块的坐标。
         * 
         * 会被回收的区块不会包含在里面，这个获取后可以缓存到后面的帧处理，
         * 毕竟通过坐标需要调用函数 getTerrainChunk 才能获取到区块
         * 
         * @param outCoordsVec 用于存放坐标
        */
        void getAllTerrainChunkCoords(std::vector<Point2> &outCoordsVec);

        /**
         * @brief 获取所有区块。
         * 
         * 会被回收的区块不会包含在里面，这个获取到后就要立即进行处理，不能留到下一帧，
         * 因为某些区块到下一帧后就可能被回收处理掉，导致出错
         * 
         * @param outChunkVec 用于存放区块
        */
        void getAllTerrainChunk(std::vector<TerrainChunk*> &outChunkVec);

    public:
        SubsystemTerrain();
        virtual ~SubsystemTerrain();

        virtual void update(float dt) override;
        virtual void load(const nlohmann::json &json) override;
        virtual void save(nlohmann::json &json) override;
        virtual void saveToMsgJson(nlohmann::json &jsonData) override;

        virtual const std::string &getName() const override;
        virtual SubUpdateType getUpdateType() override;
        virtual SubLoadEchelon getLoadEchelon() override;

    protected:
        /**
         * @brief 创建 区块 缓存，创建出来的 区块 缓存会被放进 区块 队列中
         * @param count 要创建的 区块 缓存数量
        */
        virtual bool createChunkCatch(int32_t count);

        /**
         * @brief 从 区块 队列中获得可用的 区块，
         * 
         * @note 无需设置Coords。注意：该函数没有锁保护
         * 
         * @param coordX 区块坐标X
         * @param coordY 区块坐标Y
         * @return 区块 对象 | nullptr 内存不够用了
        */
        virtual TerrainChunk *getAvailableChunk(int32_t coordX, int32_t coordY);

        /**
         * @brief 保存区块表中去区块的数据，
         * 
         * @note 只有改变过的区块需要保存，没有改变的不会保存，光照发生变化不会触发保存
        */
        virtual void saveAllChunk();

        
        virtual void loadChunk(Point2 chunk);

    public:
        std::shared_ptr<ITerrainContentsGenerator> terrainContentsGenerator;
        std::shared_ptr<ITerrainSerializer> terrainSerializer;
        std::shared_ptr<TerrainUpdater> terrainUpdater;

    protected:
        SubsystemBlockBehaviors *m_blockBehaviors;
        SubsystemPickables *m_subsystemPickables;

        float m_saveRegionTime;
        // 季节温度
        int32_t m_seasonTemperature;
        // 季节湿度
        int32_t m_seasonHumidity;


        SpinLock m_chunkMapLock;
        std::map<Point2, TerrainChunk *> m_chunkMap;
        moodycamel::ConcurrentQueue<TerrainChunk *> m_chunkQueue;
        std::vector<TerrainChunk *> m_chunkCatchVector;

        
        moodycamel::ConcurrentQueue<Point2> m_waitLoadChunkQueue;
        moodycamel::ConcurrentQueue<TerrainChunk *> m_loadFinishChunkQueue;


        SpinLock m_modifiedLock;
        std::set<Point3> m_modifiedSet;
        std::vector<Point3> m_modifiedVec;
    };

    inline TerrainChunk *SubsystemTerrain::getChunkAtCell(int32_t x, int32_t y)
    {
        return getTerrainChunk(x >> 4, y >> 4);
    }


    inline int32_t SubsystemTerrain::getCellContents(int32_t x, int32_t y, int32_t z)
    {
        return Terrain::ExtractContents(getCellValue(x, y ,z));
    }

    inline int32_t SubsystemTerrain::getCellContents(const Point3 &point)
    {
        return getCellContents(point.posX, point.posY, point.posZ);
    }

    inline int32_t SubsystemTerrain::getCellValue(const Point3 &point)
    {
        return getCellValue(point.posX, point.posY, point.posZ);
    }

    inline void SubsystemTerrain::changeCell(const Point3& point, int32_t value, ComponentMiner *miner)
    {
        changeCell(point.posX, point.posY, point.posZ, value, miner);
    }

    inline bool SubsystemTerrain::changeOldCell(const Point3 &point, int32_t oldValue, int32_t newValue, ComponentMiner *miner)
    {
        return changeOldCell(point.posX, point.posY, point.posZ, oldValue, newValue, miner);
    }

    inline int32_t SubsystemTerrain::getTemperature(int32_t x, int32_t z)
    {
        return Terrain::ExtractTemperature(getShaftValue(x, z));
    }

    inline void SubsystemTerrain::setTemperature(int32_t x, int32_t z, int32_t temperature)
    {
        setShaftValue(x, z, Terrain::ReplaceTemperature(getShaftValue(x, z), temperature));
    }

    inline int32_t SubsystemTerrain::getHumidity(int32_t x, int32_t z)
    {
        return Terrain::ExtractHumidity(getShaftValue(x, z));
    }

    inline void SubsystemTerrain::setHumidity(int32_t x, int32_t z, int32_t humidity)
    {
        setShaftValue(x, z, Terrain::ReplaceHumidity(getShaftValue(x, z), humidity));
    }

    inline int32_t SubsystemTerrain::getTopHeight(int32_t x, int32_t z)
    {
        return Terrain::ExtractTopHeight(getShaftValue(x, z));
    }

    inline void SubsystemTerrain::setTopHeight(int32_t x, int32_t z, int32_t topHeight)
    {
        setShaftValue(x, z, Terrain::ReplaceTopHeight(getShaftValue(x, z), topHeight));
    }

    inline int32_t SubsystemTerrain::getBottomHeight(int32_t x, int32_t z)
    {
        return Terrain::ExtractBottomHeight(getShaftValue(x, z));
    }

    inline void SubsystemTerrain::setBottomHeight(int32_t x, int32_t z, int32_t bottomHeight)
    {
        setShaftValue(x, z, Terrain::ReplaceBottomHeight(getShaftValue(x, z), bottomHeight));
    }

    inline int32_t SubsystemTerrain::getSeasonalTemperature(int32_t x, int32_t y)
    {
        return getTemperature(x, y) + m_seasonTemperature;
    }

    inline int32_t SubsystemTerrain::getSeasonalTemperature(int32_t shaftValue)
    {
        return Terrain::ExtractTemperature(shaftValue) + m_seasonTemperature;
    }

    inline void SubsystemTerrain::setSeasonalTemperature(int32_t x, int32_t y, int32_t seasonTemperature)
    {
        m_seasonTemperature = seasonTemperature;
    }

    inline int32_t SubsystemTerrain::getSeasonalHumidity(int32_t x, int32_t y)
    {
        return getHumidity(x, y) + m_seasonHumidity;
    }

    inline int32_t SubsystemTerrain::getSeasonalHumidity(int32_t shaftValue)
    {
        return Terrain::ExtractHumidity(shaftValue) + m_seasonHumidity;
    }

    inline void SubsystemTerrain::setSeasonalHumidity(int32_t x, int32_t y, int32_t seasonHumidity)
    {
        m_seasonHumidity = seasonHumidity;
    }

    inline int32_t SubsystemTerrain::getSeasonTemperatureValue()
    {
        return m_seasonTemperature;
    }
    
    inline void SubsystemTerrain::setSeasonTemperatureValue(int32_t value)
    {
        m_seasonTemperature = value;
    }

    inline int32_t SubsystemTerrain::getSeasonHumidityValue()
    {
        return m_seasonHumidity;
    }

    inline void SubsystemTerrain::setSeasonHumidityValue(int32_t value)
    {
        m_seasonHumidity = value;
    }

    inline int32_t SubsystemTerrain::getCellLight(int32_t x, int32_t y, int32_t z)
    {
        return Terrain::ExtractLight(getCellValue(x, y, z));
    }

    inline int32_t SubsystemTerrain::getCellLight(const Point3 &point)
    {
        return getCellLight(point.posX, point.posY, point.posZ);
    }





}

#endif